﻿using System;
using System.Runtime.CompilerServices;
using UtilZ.Dotnet.Ex.Model;

namespace UtilZ.Dotnet.Ex.Base
{
    /// <summary>
    /// 数据下采样类
    /// </summary>
    unsafe public class DownSampleHelper
    {
        /***************************************************************
         * 降采样方法：
         * 最大值法：取一定范围内最大值作为代表。
         * 最小值法：取一定范围内最小值作为代表。
         * 最大最小值法：取一定范围内最大和小值作为代表，并根据最大最小值先后顺序排列。
         * 平均值法：将一定范围内的数据点取平均值作为代表。
         * 中位数法：选取范围内的中位数值以反映整体趋势,中位数是将一组数据按大小顺序排列后，位于中间位置的数值。如果数据个数为奇数，中位数是中间的数；如果为偶数，则是中间两个数的平均值。
         * 随机采样：随机抽取部分数据点，适用于分布均匀的数据集
         * 
         * ​特殊考虑，如果是第一个桶则选取第一个点，最后一个桶则选取最后一个点，从而确保整个数据集的完整性。
         ***************************************************************/

        /// <summary>
        /// 最大值法降采样
        /// </summary>
        /// <param name="inputData">输入数据</param>
        /// <param name="outputData">输出数据</param>
        public static void Max(Span<double> inputData, Span<double> outputData)
        {
            int segSize = inputData.Length / outputData.Length;
            if (inputData.Length % outputData.Length > 0)
            {
                segSize++;
            }

            int index = segSize;
            int endIndex = outputData.Length - 1;

            outputData[0] = inputData[0];
            outputData[endIndex] = inputData[inputData.Length - 1];

            for (int i = 1; i < endIndex; i++)
            {
                outputData[i] = EnumerableEx.Max(inputData.Slice(index, segSize));
                index += segSize;
            }
        }

        /// <summary>
        /// 最小值法降采样
        /// </summary>
        /// <param name="inputData">输入数据</param>
        /// <param name="outputData">输出数据</param>
        public static void Min(Span<double> inputData, Span<double> outputData)
        {
            int segSize = inputData.Length / outputData.Length;
            if (inputData.Length % outputData.Length > 0)
            {
                segSize++;
            }

            int index = segSize;
            int endIndex = outputData.Length - 1;

            outputData[0] = inputData[0];
            outputData[endIndex] = inputData[inputData.Length - 1];

            for (int i = 1; i < endIndex; i++)
            {
                outputData[i] = EnumerableEx.Min(inputData.Slice(index, segSize));
                index += segSize;
            }
        }

        /// <summary>
        /// 最大最小值法降采样
        /// </summary>
        /// <param name="inputData">输入数据</param>
        /// <param name="outputData">输出数据</param>
        public static void MaxMin(Span<double> inputData, Span<double> outputData)
        {
            int segCount = outputData.Length / 2;

            int segSize = inputData.Length / segCount;
            if (inputData.Length % segCount > 0)
            {
                segSize++;
            }

            int offset = 0, index = 0;
#if NET4_0
            double* inputDataPtr = inputData.DataPtr;
#else
            double* inputDataPtr = (double*)Unsafe.AsPointer<double>(ref inputData[0]);
#endif


            //第一段
            MaxMinValueIndexInfo<double> mmvii = EnumerableEx.FindMinMaxIndex(inputDataPtr + offset, segSize);
            offset += segSize;
            outputData[index] = inputData[0];
            index++;
            if (mmvii.MaxValueIndex.Value < mmvii.MinValueIndex.Value)
            {
                outputData[index] = mmvii.MinValue.Value;
                index++;
            }
            else
            {
                outputData[index] = mmvii.MaxValue.Value;
                index++;
            }

            for (int i = 1; i < segCount; i++)
            {
                mmvii = EnumerableEx.FindMinMaxIndex(inputDataPtr + offset, segSize);
                offset += segSize;

                if (mmvii.MaxValueIndex.Value < mmvii.MinValueIndex.Value)
                {
                    outputData[index] = mmvii.MaxValue.Value;
                    index++;
                    outputData[index] = mmvii.MinValue.Value;
                    index++;
                }
                else
                {
                    outputData[index] = mmvii.MinValue.Value;
                    index++;
                    outputData[index] = mmvii.MaxValue.Value;
                    index++;
                }
            }
        }

        /// <summary>
        /// 平均值法降采样
        /// </summary>
        /// <param name="inputData">输入数据</param>
        /// <param name="outputData">输出数据</param>
        public static void Average(Span<double> inputData, Span<double> outputData)
        {
            int segSize = inputData.Length / outputData.Length;
            if (inputData.Length % outputData.Length > 0)
            {
                segSize++;
            }

            int index = segSize;
            int endIndex = outputData.Length - 1;

            outputData[0] = inputData[0];
            outputData[endIndex] = inputData[inputData.Length - 1];

            for (int i = 1; i < endIndex; i++)
            {
                outputData[i] = EnumerableEx.Average(inputData.Slice(index, segSize));
                index += segSize;
            }
        }

        /// <summary>
        /// 中位数法降采样
        /// </summary>
        /// <param name="inputData">输入数据</param>
        /// <param name="outputData">输出数据</param>
        public static void Middle(Span<double> inputData, Span<double> outputData)
        {
            int segSize = inputData.Length / outputData.Length;
            if (inputData.Length % outputData.Length > 0)
            {
                segSize++;
            }

            int index = segSize;
            int endIndex = outputData.Length - 1;

            outputData[0] = inputData[0];
            outputData[endIndex] = inputData[inputData.Length - 1];

            using (var tempPointer = new UnmanagedMemoryPointer<double>(segSize))
            {
                var segSortData = tempPointer.GetSpan();
                bool segSizeIsEvenInteger = MathEx.IsEvenInteger(segSize);
                int midIndex1 = segSize / 2;
                int midIndex2 = midIndex1 + 1;

                for (int i = 1; i < endIndex; i++)
                {
                    inputData.Slice(index, segSize).CopyTo(segSortData);
                    segSortData.Sort();

                    if (segSizeIsEvenInteger)
                    {
                        outputData[i] = (segSortData[midIndex1] + segSortData[midIndex2]) / 2;
                    }
                    else
                    {
                        outputData[i] = segSortData[midIndex1];
                    }

                    index += segSize;
                }
            }
        }

        /// <summary>
        /// 随机采样降采样
        /// </summary>
        /// <param name="inputData">输入数据</param>
        /// <param name="outputData">输出数据</param>
        public static void Random(Span<double> inputData, Span<double> outputData)
        {
            int segSize = inputData.Length / outputData.Length;
            if (inputData.Length % outputData.Length > 0)
            {
                segSize++;
            }

            int index = segSize;
            int endIndex = outputData.Length - 1;

            outputData[0] = inputData[0];
            outputData[endIndex] = inputData[inputData.Length - 1];
            Random rnd = new Random(Environment.TickCount);

            for (int i = 1; i < endIndex; i++)
            {
                outputData[i] = inputData[index + rnd.Next(0, segSize)];
                index += segSize;
            }
        }



    }
}
